package com.shelpe.services.algorithm.controller;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.algorithm.client.RankClient;
import com.shelpe.services.algorithm.client.SinbadClient;
import com.shelpe.services.algorithm.client.SinbadClient.RptKeyword;
import com.shelpe.services.algorithm.constant.MobileRank;
import com.shelpe.services.algorithm.constant.PCRank;
import com.shelpe.services.algorithm.entity.batis.Keyword;
import com.shelpe.services.algorithm.entity.batis.KeywordExample;
import com.shelpe.services.algorithm.entity.jpa.Adgroup;
import com.shelpe.services.algorithm.entity.jpa.Campaign;
import com.shelpe.services.algorithm.entity.jpa.Tactics;
import com.shelpe.services.algorithm.entity.jpa.WantRank;
import com.shelpe.services.algorithm.entity.jpa.WantRank.Doing;
import com.shelpe.services.algorithm.entity.mongo.KeywordRank;
import com.shelpe.services.algorithm.ranking.RankingSustainRule;
import com.shelpe.services.algorithm.ranking.RankingSustainer;
import com.shelpe.services.algorithm.repository.batis.KeywordMapper;
import com.shelpe.services.algorithm.repository.jpa.AdgroupRepository;
import com.shelpe.services.algorithm.repository.jpa.CampaignRepository;
import com.shelpe.services.algorithm.repository.jpa.TacticsRepository;
import com.shelpe.services.algorithm.repository.jpa.WantRankRepository;
import com.shelpe.services.algorithm.repository.mongo.KeywordRankRepository;
import com.shelpe.services.algorithm.service.RealtimeRankService;
import com.shelpe.services.algorithm.sustain.SustainRule.SustainStatus;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/algorithmservice")
@Slf4j
public class RankingController {

	@Autowired
	private RankingSustainRule rule;
	@Autowired
	private TacticsRepository tacticsRepository;
	@Autowired
	private WantRankRepository wantRankRepository;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private KeywordRankRepository keywordRankRepository;
	@Autowired
	private RankClient rankClient;
	@Autowired
	private CampaignRepository campaignRepository;
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private SinbadClient sinbadClient;

	@Autowired
	private RealtimeRankService realtimeRankService;

	@Value("${spring.application.name}")
	private String applicationName;
	@Value("${algorithm.allow.ip:221.176.172.86}")
	private String allowedIP;

	@RequestMapping(path = "/users/{user_id}/@sync", method = RequestMethod.GET)
	@ResponseBody
	public RankingsSyncRes syncRankings(@PathVariable("user_id") long userID) {
		RankingsSyncRes res = new RankingsSyncRes();
		log.debug("try to sync {} user's ranking", userID);

		List<Tactics> tactics = this.tacticsRepository.findByUserID(userID);
		List<Long> ids = new ArrayList<Long>();
		for (Tactics tactic : tactics) {
			if (tactic.getKeywordIDs() != null) {
				String[] keywordIDs = tactic.getKeywordIDs().split(",");
				for (String idStr : keywordIDs) {
					long id = NumberUtils.toLong(idStr);
					if (id > 0) {
						ids.add(id);
					}
				}
			}

		}
		log.debug("need to sync {} keyword ids", ids.size());

		if (ids.size() > 0) {
			KeywordExample ke = new KeywordExample();
			ke.setUserId(userID);
			ke.createCriteria().andKeywordIdIn(ids);
			List<Keyword> keywords = this.keywordMapper.selectByExample(ke);
			log.debug("find {} keywords from db", keywords.size());

			List<KeywordRank> ranks = new ArrayList<KeywordRank>();
			for (Keyword keyword : keywords) {
				RankClient.KeywordRankItemResponse rankRes = this.rankClient.findKeywordRanks(keyword.getNick(),
						keyword.getWord(), allowedIP);
				if (res != null && rankRes.getRes() != null && rankRes.getRes().getDataList() != null
						&& rankRes.getRes().getDataList().getItems() != null) {
					for (RankClient.KeywordRankItem item : rankRes.getRes().getDataList().getItems()) {
						KeywordRank rank = new KeywordRank();
						rank.setNick(keyword.getNick());
						rank.setKeywordID(keyword.getKeywordId());
						rank.setKeyword(keyword.getWord());
						rank.setUserID(userID);
						rank.setRank(new KeywordRank.KeywordRankItem());
						BeanUtils.copyProperties(item, rank.getRank());
						rank.setCreatedTime(new Date());
						rank.setModifiedTime(new Date());
						ranks.add(rank);
					}
				} else {
					log.warn("fail to fetch rank from taobao: {}", keyword);
				}
			}

			if (ranks.size() > 0) {
				log.debug("save {} ranks to mongo", ranks.size());
				this.keywordRankRepository.save(ranks);
				res.setStatus(true);
				res.setTotal(ranks.size());
			}
		}

		return res;
	}

	@Data
	public static class RankingsSyncRes {
		private boolean status;
		private int total;
	}

	@RequestMapping(path = "/rankings/{ranking_id}/@execute", method = RequestMethod.GET)
	@ResponseBody
	public RankingRes executeRanking(@PathVariable("ranking_id") long rankingID) {
		log.info("start executeRanking: {}", rankingID);
		Tactics tactics = this.tacticsRepository.findOne(rankingID);
		Assert.notNull(tactics, "tactics_not_found");
		Assert.isTrue(tactics.getStatus() == Tactics.Status.starting, "status_is_not_starting");
		if (tactics.getFromTime() != null && tactics.getToTime() != null) {
			Date today = new Date();
			boolean between = DateUtils.truncatedCompareTo(tactics.getFromTime(), today, Calendar.MINUTE) <= 0
					&& DateUtils.truncatedCompareTo(today, tactics.getToTime(), Calendar.MINUTE) <= 0;

			if (!between && DateUtils.truncatedCompareTo(tactics.getToTime(), today, Calendar.MINUTE) < 0) {
				// set as finish status if expired
				tactics.setStatus(Tactics.Status.finish);
				tactics.setModifiedTime(new Date());
				this.tacticsRepository.save(tactics);
			}
			Assert.isTrue(between, "not_in_range");
		}

		RankingSustainer sustainer = new RankingSustainer();
		sustainer.setTactics(tactics);
		RankingRes dto = new RankingRes();
		boolean res = this.execute(sustainer);
		dto.setStatus(res);
		dto.setResults(sustainer.getResults());

		if(res){
			tactics.setExecutions(tactics.getExecutions() + 1);
		}
		tactics.setModifiedTime(new Date());
		tactics.setNextTime(DateUtils.addSeconds(new Date(), tactics.getCampaignPeriod()));
		this.tacticsRepository.save(tactics);
		return dto;
	}

	@RequestMapping(path = "/users/{user_id}/rankings", method = RequestMethod.POST)
	@ResponseBody
	public RankingDto createRanking(@PathVariable("user_id") long userID, @RequestBody RankingDto dto) {
		Assert.notNull(dto.getName());
		Assert.notNull(dto.getPremium());

		Tactics tactics = new Tactics();
		BeanUtils.copyProperties(dto, tactics);
		tactics.setUserID(userID);
		tactics.setCreateTime(new Date());
		tactics.setModifiedTime(new Date());
		StringBuffer buffer = new StringBuffer();
		for (KeywordRanking ranking : dto.getKeywords()) {
			buffer.append(ranking.getKeywordID()).append(",");
		}
		tactics.setKeywordIDs(buffer.substring(0, buffer.length() - 1));
		tactics.setPremium(dto.getPremium().to());
		this.tacticsRepository.save(tactics);

		for (KeywordRanking ranking : dto.getKeywords()) {
			WantRank rank = new WantRank();
			rank.setTacticsID(tactics.getTacticsID());
			rank.setKeywordID(ranking.getKeywordID());
			rank.setWantRank(ranking.getRank());
			rank.setOriginalValue(ranking.getPrice());
			rank.setUpdatedTime(new Date());
			rank.setDoing(Doing.executable);
			this.wantRankRepository.save(rank);
		}

		dto.setRankingID(tactics.getTacticsID());

		return dto;
	}

	@RequestMapping(path = "/users/{user_id}/rankings/{ranking_id}", method = RequestMethod.PUT)
	@ResponseBody
	public RankingDto updateRanking(@PathVariable("user_id") long userID, @PathVariable("ranking_id") long rankingID,
			@RequestBody RankingDto dto) {
		Tactics tactics = this.tacticsRepository.findOne(rankingID);

		BeanUtils.copyProperties(dto, tactics);
		tactics.setTacticsID(rankingID);
		tactics.setCreateTime(new Date());
		StringBuffer buffer = new StringBuffer();
		for (KeywordRanking ranking : dto.getKeywords()) {
			buffer.append(ranking.getKeywordID()).append(",");
		}
		tactics.setKeywordIDs(buffer.substring(0, buffer.length() - 1));
		tactics.setPremium(dto.getPremium().to());
		tactics.setModifiedTime(new Date());
		this.tacticsRepository.save(tactics);

		List<WantRank> ranks = this.wantRankRepository.findByTacticsID(rankingID);
		if(ranks.size() > 0){
			this.wantRankRepository.delete(ranks);
		}
		
		for (KeywordRanking ranking : dto.getKeywords()) {
			WantRank rank = new WantRank();
			rank.setTacticsID(tactics.getTacticsID());
			rank.setKeywordID(ranking.getKeywordID());
			rank.setWantRank(ranking.getRank());
			rank.setOriginalValue(ranking.getPrice());
			rank.setUpdatedTime(new Date());
			rank.setDoing(Doing.executable);
			this.wantRankRepository.save(rank);
		}
		return dto;
	}

	@RequestMapping(path = "/users/{user_id}/rankings/{ranking_id}", method = RequestMethod.GET)
	@ResponseBody
	public RankingDetailDto detailRanking(@PathVariable("user_id") long userID, @PathVariable("ranking_id") long rankingID){
		log.debug("detailRanking ranking_id:{}", rankingID);
		RankingDetailDto dto = new RankingDetailDto();
		Tactics tactics = this.tacticsRepository.findOne(rankingID);
		this.toDto(tactics, dto);
		List<WantRank> wantRanks = wantRankRepository.findByTacticsID(tactics.getTacticsID());
		for(WantRank wantRank : wantRanks){
			RankingItemDetailDto itemDto = new RankingItemDetailDto();
			itemDto.setWantRank(wantRank);
			
			Keyword keyword = keywordMapper.selectByPrimaryKey(wantRank.getKeywordID(), userID);
			if(keyword != null){
				itemDto.setWord(keyword.getWord());
				itemDto.setKeyword(keyword);
				
				Campaign campaign = campaignRepository.findOne(keyword.getCampaignId());
				if(campaign != null){
					itemDto.setCampaignName(campaign.getTitle());
				}
				Adgroup adgroup = adgroupRepository.findOne(keyword.getAdgroupId());
				if(adgroup != null){
					itemDto.setAdgroupName(adgroup.getProductName());
				}
			}
			RptKeyword rptKeyword = sinbadClient.keywordData(userID, wantRank.getKeywordID());
			if(rptKeyword != null){
				itemDto.setReport(rptKeyword);
			}
			
			dto.addItem(itemDto);
		}
		return dto;
	}
	
	@Data
	public static class RankingDetailDto extends RankingDto{
		private List<RankingItemDetailDto> items;
		
		public void addItem(RankingItemDetailDto item){
			if(this.items == null){
				this.items = new ArrayList<RankingItemDetailDto>();
			}
			this.items.add(item);
		}
	}
	
	@Data
	public static class RankingItemDetailDto{
		@JsonProperty("campaign_name")
		private String campaignName;
		@JsonProperty("adgroup_name")
		private String adgroupName;
		private String word;
		private Keyword keyword;
		@JsonProperty("want_rank")
		private WantRank wantRank;
		private RptKeyword report;
	}
	
	@RequestMapping(path = "/users/{user_id}/rankings", method = RequestMethod.GET)
	@ResponseBody
	public List<RankingDto> findRankings(@PathVariable("user_id")long userID){
		List<Tactics> tacticses = this.tacticsRepository.findByUserIDOrderByCreateTimeDesc(userID); //此时拿到了所有的userID集合
		
		List<RankingDto> dtos = new ArrayList<RankingDto>();  //设置接收userID的集合的类。
		for(Tactics tactics: tacticses){      //遍历集合并将dto所需要的字段添加到dtos内
			RankingDto dto = new RankingDto();
			this.toDto(tactics, dto);
			dtos.add(dto);
		}
		return dtos;
	}
	
	private void toDto(Tactics tactics, RankingDto dto){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		BeanUtils.copyProperties(tactics, dto);
		dto.setRankingID(tactics.getTacticsID());
		dto.setName(tactics.getName());
		dto.setLastModifiedTime(sdf.format(tactics.getModifiedTime()));
		dto.setStatus(tactics.getStatus().getValue());
		dto.setExeCount(tactics.getExecutions());
		dto.setFromTime(tactics.getFromTime());
		dto.setToTime(tactics.getToTime());
		if(tactics.getPremium() != null){
			dto.setPremium(new RankingPremium());
			dto.getPremium().setValue(tactics.getPremium().getValue());
			if(tactics.getPremium().isPercent()){
				dto.getPremium().setType(0);
			}
		}
			
	}

	@RequestMapping(path = "/users/{user_id}/realtimeRanks", method = RequestMethod.GET)
	@ResponseBody
	public List<KeywordRealtimeRank> findKeywordRealtimeRanks(@PathVariable("user_id") long userID,
			@RequestParam("keyword_ids") List<Long> keywordIDs,
			@RequestParam(name = "device", defaultValue = "1") int device) {
		List<KeywordRealtimeRank> dtos = new ArrayList<KeywordRealtimeRank>();

		KeywordExample ke = new KeywordExample();
		ke.setUserId(userID);
		ke.createCriteria().andKeywordIdIn(keywordIDs);
		List<Keyword> keywords = this.keywordMapper.selectByExample(ke);

		Map<Long, List<Long>> adgoupKeywordIDs = new HashMap<Long, List<Long>>();
		String nick = null;
		for (Keyword keyword : keywords) {
			if (!adgoupKeywordIDs.containsKey(keyword.getAdgroupId())) {
				adgoupKeywordIDs.put(keyword.getAdgroupId(), new ArrayList<Long>());
			}
			adgoupKeywordIDs.get(keyword.getAdgroupId()).add(keyword.getKeywordId());
			nick = keyword.getNick();
		}

		for (long adgroupID : adgoupKeywordIDs.keySet()) {
			List<RankClient.KeywordRealtimeRankItem> ranks = this.realtimeRankService.syncRanks(nick, adgroupID,
					adgoupKeywordIDs.get(adgroupID));
			if (ranks != null) {
				for (RankClient.KeywordRealtimeRankItem item : ranks) {
					KeywordRealtimeRank rank = new KeywordRealtimeRank();
					rank.setKeywordID(item.getKeywordID());
					Keyword kw = this.filterKeyword(keywords, item.getKeywordID());
					if (kw != null) {
						rank.setWord(kw.getWord());
					}
					if (device == 1) {
						PCRank pcRank = PCRank.from(item.getPcRank());
						rank.setRank(pcRank.getValue());
						rank.setRankName(pcRank.name());
					} else {
						MobileRank mRank = MobileRank.from(item.getPcRank());
						rank.setRank(mRank.getValue());
						rank.setRankName(mRank.name());
					}
					dtos.add(rank);
				}
			}
		}

		return dtos;
	}

	private Keyword filterKeyword(List<Keyword> keywords, long keywordID) {
		for (Keyword keyword : keywords) {
			if (keyword.getKeywordId() == keywordID) {
				return keyword;
			}
		}
		return null;
	}

	@Data
	public static class KeywordRealtimeRank {
		@JsonProperty("keyword_id")
		private long keywordID;
		private String word;
		private int rank;
		@JsonProperty("rank_name")
		private String rankName;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class RankFindDto {
		@JsonProperty("tactics_id")
		private long rankingID;

		@JsonProperty("tactics_name")
		private String name;
		@JsonProperty("from")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date fromTime;
		@JsonProperty("to")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date toTime;
		@JsonProperty("tactics_status")
		private int status;
		private int campaignPeriod;
		@JsonProperty("heighest_price")
		private int heighestPrice;

		@JsonProperty("executions")
		private long exeCount;// 执行次数
		@JsonProperty("tactics_modified_time")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date lastModifiedTime;// 上一次修改时间
	}

	private boolean execute(RankingSustainer sustainer) {
		log.info("start execute {} word ranking", sustainer.getTactics());
		boolean res = false;

		boolean continued = this.rule.check(sustainer);
		if (continued) {
			continued = this.rule.prepare(sustainer);
			if (continued) {
				SustainStatus status = this.rule.sustain(sustainer);
				if (status != SustainStatus.exceptional) {
					this.rule.maintain(sustainer, status);
					this.rule.mark(sustainer);
					res = true;
					log.debug("finish execute {} word ranking", sustainer.getTactics());
				} else {
					log.info("{} word fail to sustain", sustainer.getTactics());
				}
			} else {
				log.info("{} word is not ready", sustainer.getTactics());
			}
		} else {
			log.info("{} word does not need execute", sustainer.getTactics());
		}
		return res;
	}

	@Data
	public static class RankingRes {
		private boolean status;
		private List<RankingResult> results;

		public void add(RankingResult res) {
			if (this.results == null) {
				this.results = new ArrayList<RankingResult>();
			}
			this.results.add(res);
		}
	}

	@Data
	public static class RankingResult {
		@JsonProperty("keyword_id")
		private long keywordID;
		private boolean result;
	}

	@Data
	@JsonInclude(Include.NON_NULL)
	public static class RankingDto {
		@JsonProperty("tactics_id")
		private long rankingID;

		@JsonProperty("tactics_name")
		private String name;
		@JsonProperty("from")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date fromTime;
		@JsonProperty("to")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private Date toTime;
		@JsonProperty("tactics_status")
		private int status;
		private int device;
		private RankingPremium premium;
		@JsonProperty("campaign_period")
		private int campaignPeriod;
		@JsonProperty("heighest_price")
		private int heighestPrice;
		@JsonProperty("keywords")
		private List<KeywordRanking> keywords;

		@JsonProperty("executions")
		private long exeCount;// 执行次数
		@JsonProperty("tactics_modified_time")
		@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
		private String lastModifiedTime;// 上一次修改时间

	}

	@Data
	public static class KeywordRanking {
		@JsonProperty("keyword_id")
		private long keywordID;
		@JsonProperty("want_rank")
		private int rank;
		private int price;
	}

	@Data
	public static class RankingPremium {
		private int type;
		private float value;

		public Tactics.Premium to() {
			Tactics.Premium p = new Tactics.Premium();
			p.setValue(value);
			p.setPercent(type == 0);
			return p;
		}
	}
}
